package routes_api

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"github.com/iris-contrib/middleware/cors"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/context"
	"github.com/speps/go-hashids"
	"my_iris/utils"
	"strings"
	"time"
)

//mwCors 中间件 cors
var mwCors = cors.New(cors.Options{
	Debug:            false,
	AllowCredentials: true,
	AllowedOrigins:   []string{"*"}, // 允许通过的主机名称
	AllowedMethods: []string{
		"GET",
		"POST",
		"PUT",
		"PATCH",
		"DELETE",
		"OPTIONS",
		"HEAD",
	},
	AllowedHeaders: []string{
		"Origin",
		"Content-Type",
		"Cookie",
		"Accept",
		"Authorization",
		"Access-Control-Allow-Origin",
		"X-Requested-With",
	},
})

// --------------------------------------前置中间件---------------------------------------

// 前置中间件
var mwBefore = NewMiddlewareBefore()

// 前置中间件
func NewMiddlewareBefore() context.Handler {
	return func(ctx iris.Context) {
		requestToken := GetAuthorizationBearer(ctx)
		var userId int
		var isLogin bool
		isLogin, userId = MyTokenTool.IsValid(requestToken)
		ctx.Values().Set("isLogin", isLogin)
		ctx.Values().Set("userId", userId)
		ctx.Next()
	}
}

func IsLogin(ctx iris.Context) (isLogin bool) {
	isLogin = ctx.Values().GetBoolDefault("isLogin", false)
	return
}

func GetUserId(ctx iris.Context) (userId int) {
	userId = ctx.Values().GetIntDefault("userId", 0)
	return
}

func GetUserIdInt64(ctx iris.Context) (userId int64) {
	userId = ctx.Values().GetInt64Default("userId", 0)
	return
}

func GetAuthorizationBearer(ctx iris.Context) (authBearer string) {
	authBearer = ctx.GetHeader("Authorization")
	authBearer = strings.TrimSpace(strings.TrimPrefix(authBearer, "Bearer"))
	return
}

// --------------------------------------自定义token---------------------------------------

//Token 自定义token
var MyTokenTool *TokenTool

func init() {
	MyTokenTool = NewMyToken("123456", 20)
}

const (
	TokenCachePrefix = "tk:"          // Redis中会话缓存键前缀
	TokenCacheExpire = 3600 * 24 * 30 // Redis中会话缓存时长
)

//TokenTool 自定义token
//token生成方式：
//   1.token = hashids.encode(userId,当前时间戳)
//   2.保存到redis, user:{userId}:token => 1, 有效期 30 天
//   3.验证, redis中存在第2步的键则有效, 否则登录失效
//   注: token中本身包含有效期, 但是部分情况可能需要提前手动失效，从redis中手动删除，则token虽然在有效期内但依然失效
type TokenTool struct {
	hashSalt    string
	tokenLength int
}

func NewMyToken(hashSalt string, tokenLength int) (tk *TokenTool) {
	tk = &TokenTool{}
	tk.hashSalt = hashSalt
	tk.tokenLength = tokenLength
	if tk.hashSalt == "" {
		tk.hashSalt = "defaultSalt"
	}
	if tk.tokenLength < 10 {
		tk.tokenLength = 20
	}
	return tk
}

func (p *TokenTool) Encode(nums ...int) (token string) {
	hd := hashids.NewData()
	hd.Salt = p.hashSalt
	hd.MinLength = p.tokenLength
	h, _ := hashids.NewWithData(hd)
	token, _ = h.Encode(nums)
	return
}

func (p *TokenTool) Decode(token string) (nums []int) {
	hd := hashids.NewData()
	hd.Salt = p.hashSalt
	hd.MinLength = p.tokenLength
	h, _ := hashids.NewWithData(hd)
	nums, _ = h.DecodeWithError(token)
	return
}

// 生成一个token并保存到redis
func (p *TokenTool) Save(userId int) (token string, err error) {
	now := time.Now().Unix()
	token = p.Encode(userId, int(now))
	cacheKey := p.GenCacheKey(userId, token)

	// 存到redis
	redisC := utils.GetRedisConn()
	_, err = redisC.Do("SETEX", cacheKey, TokenCacheExpire, 1)
	return
}

// 判断一个token是否有效
func (p *TokenTool) IsValid(token string) (valid bool, userId int) {
	nums := p.Decode(token)
	// 解码错误
	if len(nums) != 2 {
		return
	}
	// 超过有效期
	_now := time.Now().Unix()
	now := int(_now)
	if now-nums[1] >= TokenCacheExpire {
		return
	}
	cacheKey := p.GenCacheKey(nums[0], token)
	// redis中是否存在
	redisC := utils.GetRedisConn()
	_, err := redis.String(redisC.Do("GET", cacheKey))
	if err != nil {
		// 不存在, 无效
		return
	}
	// token有效
	valid = true
	userId = nums[0]
	return
}

func (p *TokenTool) GenCacheKey(userId int, token string) (key string) {
	key = TokenCachePrefix + fmt.Sprintf("user:%d:%s", userId, token)
	return
}

// -------------------------------------- ---------------------------------------

// 中间件: 需要登录
var needLogin = NewMiddlewareNeedLogin()

// 前置中间件
func NewMiddlewareNeedLogin() context.Handler {
	return func(ctx iris.Context) {
		if !IsLogin(ctx) {
			ctx.StatusCode(iris.StatusUnauthorized)
			return
		}
		ctx.Next()
	}
}
